﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using EpiNEXT.Services.Fields;
using EpiNEXT.Services.Validation.Functions;

namespace EpiNEXT.Services.Validation
{
    //[Serializable()]
    public sealed class Token
    {
        private string _positionString = "L";
        private TokenPositions _position = TokenPositions.Left;
        private string _tokenTypeString = "FieldName";
        private TokenTypes _tokenType = TokenTypes.FieldName;

        [XmlIgnore]
        public TokenPositions Position
        {
            get { return this._position; }
            set
            {
                if (Position != value)
                {
                    this._position = value;
                    switch (Position)
                    {
                        default:
                        case TokenPositions.Left:
                            PositionString = "L";
                            break;
                        case TokenPositions.Right:
                            PositionString = "R";
                            break;
                        case TokenPositions.Center:
                            PositionString = "C";
                            break;
                    }
                }
            }
        }

        [XmlAttribute("Pos")]
        public string PositionString
        {
            get { return this._positionString; }
            set
            {
                if (!PositionString.Equals(value))
                {
                    this._positionString = value;
                    switch (PositionString)
                    {
                        default:
                        case "L":
                            Position = TokenPositions.Left;
                            break;
                        case "C":
                            Position = TokenPositions.Center;
                            break;
                        case "R":
                            Position = TokenPositions.Right;
                            break;
                    }
                }
            }
        }

        [XmlIgnore]
        public TokenTypes TokenType
        {
            get { return this._tokenType; }
            set
            {
                if (TokenType != value)
                {
                    this._tokenType = value;
                    switch (TokenType)
                    {
                        default:
                        case TokenTypes.FieldName:
                            TokenTypeString = "FieldName";
                            break;
                        case TokenTypes.Function:
                            TokenTypeString = "Function";
                            break;
                        case TokenTypes.Literal:
                            TokenTypeString = "Literal";
                            break;
                        case TokenTypes.Expression:
                            TokenTypeString = "Expression";
                            break;
                    }
                }
            }
        }

        [XmlAttribute("TokenType")]
        public string TokenTypeString
        {
            get { return this._tokenTypeString; }
            set
            {
                if (!TokenTypeString.Equals(value))
                {
                    this._tokenTypeString = value;
                    switch (TokenTypeString)
                    {
                        default:
                        case "FieldName":
                            TokenType = TokenTypes.FieldName;
                            break;
                        case "Function":
                            TokenType = TokenTypes.Function;
                            break;
                        case "Literal":
                            TokenType = TokenTypes.Literal;
                            break;
                        case "Expression":
                            TokenType = TokenTypes.Expression;
                            break;
                    }
                }
            }
        }

        [XmlText]
        public string Value { get; set; }

        [XmlElement("Years", typeof(Years))]
        [XmlElement("Year", typeof(Year))]
        [XmlElement("Months", typeof(Months))]
        [XmlElement("Month", typeof(Month))]
        public FunctionBase Function { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        public Token()
            : base()
        {
        }

        /// <summary>
        /// Gets a list of all of the fields associated with this token
        /// </summary>
        /// <param name="form">The form on which to find the fields</param>
        /// <returns>list; all fields in the token</returns>
        public List<IField> GetFields(DataEntryInstrument form)
        {
            List<IField> fields = new List<IField>();

            if (TokenType == TokenTypes.FieldName)
            {
                if (form.Fields.Contains(this.Value))
                {
                    IField field = form.Fields[this.Value];
                    fields.Add(field);
                }
            }
            else if (TokenType == TokenTypes.Function && Function != null)
            {
                fields.AddRange(Function.GetFields(form));
            }

            return fields;
        }

        /// <summary>
        /// Gets the value associated with this token
        /// </summary>
        /// <param name="record">The record on which to evaluate the token</param>
        /// <returns>object; represents the value of the token</returns>
        public object GetValue(Record record)
        {
            DataEntryInstrument form = record.Form;

            if (TokenType == TokenTypes.Literal)
            {
                return Value;
            }
            else if (TokenType == TokenTypes.FieldName)
            {
                string fieldName = this.Value;

                if(form.Fields.Contains(fieldName)) 
                {
                    IField field = form.Fields[fieldName];

                    object fieldValue = record.GetFieldData(field);
                    return fieldValue;
                }
            }
            else if (TokenType == TokenTypes.Function)
            {
                return Function.Execute(record);
            }
            else if (TokenType == TokenTypes.Expression)
            {
                string trimmedValue = this.Value.Trim().Replace("\n", String.Empty);

                Eval evaluator = new Eval();

                evaluator.ProcessFunction += evaluator_ProcessFunction;
                evaluator.ProcessSymbol += evaluator_ProcessSymbol;

                object value = evaluator.Execute(trimmedValue, record);

                evaluator.ProcessFunction -= evaluator_ProcessFunction;
                evaluator.ProcessSymbol -= evaluator_ProcessSymbol;

                return value;
            }

            return null; // Throw exception instead?
        }

        void evaluator_ProcessSymbol(object sender, SymbolEventArgs e)
        {
            string name = e.Name;

            IField field = null;
            DataEntryInstrument form = e.Record.Form;
            if (form.Fields.Contains(name))
            {
                field = form.Fields[name];
            }

            object data = e.Record.GetFieldData(field);

            if (data != null)
            {
                if (data is DateTime)
                {
                    long ticks = ((DateTime)data).Ticks;
                    e.Result = ticks / 1000;
                }
                else if (data is Decimal)
                {
                    e.Result = (double)data;
                }
                else if (data is Boolean)
                {
                    bool? result = (bool?)data;

                    if (result.HasValue)
                    {
                        if(result.Value == true)
                        {
                            e.Result = 1;
                        }
                        else
                        {
                            e.Result = 0;
                        }
                    }

                    e.Status = SymbolStatus.UndefinedSymbol;
                }
                else if (data is String)
                {
                    double d;
                    bool success = Double.TryParse(data.ToString(), out d);
                    if (success)
                    {
                        e.Result = (double)data;
                    }
                    else
                    {
                        e.Status = SymbolStatus.UndefinedSymbol;
                    }
                }
            }
            else
            {
                e.Status = SymbolStatus.UndefinedSymbol;
            }
        }

        void evaluator_ProcessFunction(object sender, FunctionEventArgs e)
        {
            if (String.Compare(e.Name, "abs", StringComparison.OrdinalIgnoreCase) == 0)
            {
                if (e.Parameters.Count == 1)
                    e.Result = Math.Abs(e.Parameters[0]);
                else
                    e.Status = FunctionStatus.WrongParameterCount;
            }
            else if (String.Compare(e.Name, "pow", StringComparison.OrdinalIgnoreCase) == 0)
            {
                if (e.Parameters.Count == 2)
                    e.Result = Math.Pow(e.Parameters[0], e.Parameters[1]);
                else
                    e.Status = FunctionStatus.WrongParameterCount;
            }
            else if (String.Compare(e.Name, "round", StringComparison.OrdinalIgnoreCase) == 0)
            {
                if (e.Parameters.Count == 1)
                    e.Result = Math.Round(e.Parameters[0]);
                else
                    e.Status = FunctionStatus.WrongParameterCount;
            }
            else if (String.Compare(e.Name, "sqrt", StringComparison.OrdinalIgnoreCase) == 0)
            {
                if (e.Parameters.Count == 1)
                    e.Result = Math.Sqrt(e.Parameters[0]);
                else
                    e.Status = FunctionStatus.WrongParameterCount;
            }
            else if (String.Compare(e.Name, "years", StringComparison.OrdinalIgnoreCase) == 0)
            {
                if (e.Parameters.Count == 2)
                {
                    // result
                    EpiNEXT.Services.Validation.Functions.Years years = new Years();
                    object value = years.Evaluate(e.Record, e.Parameters[0], e.Parameters[1]);
                    if (value != null)
                    {
                        e.Result = Convert.ToDouble(value);
                    }
                    else
                    {
                        e.Result = Double.NaN;
                    }
                }
                else
                {
                    e.Status = FunctionStatus.WrongParameterCount;
                }
            }
            // Unknown function name
            else
            {
                e.Status = FunctionStatus.UndefinedFunction;
            }

        }
    }
}
